package cz.zcu.fav.rat.managed.bean.calendar;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;

import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;
import org.primefaces.context.RequestContext;
import org.primefaces.event.CloseEvent;
import org.primefaces.event.ScheduleEntryMoveEvent;
import org.primefaces.event.ScheduleEntryResizeEvent;
import org.primefaces.event.SelectEvent;
import org.primefaces.model.ScheduleModel;

import cz.zcu.fav.rat.dataaccess.service.SettingsFactory;
import cz.zcu.fav.rat.dataaccess.service.ObjectService;
import cz.zcu.fav.rat.dataaccess.service.PeriodService;
import cz.zcu.fav.rat.dataaccess.service.ReservationService;
import cz.zcu.fav.rat.dataaccess.service.TimeSlot;
import cz.zcu.fav.rat.dataaccess.service.UserService;
import cz.zcu.fav.rat.dataaccess.service.impl.OccupiedReservationTimeSlotException;
import cz.zcu.fav.rat.managed.bean.SpringBeanFactory;
import cz.zcu.fav.rat.managed.locale.LocaleMessageFactory;

import java.io.Serializable;
import java.util.Date;

import cz.zcu.fav.rat.beans.Period;
import cz.zcu.fav.rat.beans.Reservation;
import cz.zcu.fav.rat.beans.User;

public abstract class AbstractCalendarBean<T> implements Serializable
{
	private static final long serialVersionUID = 4957953137905232306L;

	public static String DATE_VIEW_PATTERN = "dd/MM/yyyy HH:mm";
	private T item;
	private ScheduleModel eventModel;
	private ReservationEvent event;
	private Integer id;
	private String errorMessage;

	private transient ObjectService objectService;
	private transient ReservationService reservationService;
	private transient UserService userService;
	private transient PeriodService periodService;
	private SettingsFactory settingsFactory;

	protected abstract ScheduleModel getModel(Integer identifier);

	protected abstract T loadItem(Integer itemId);

	public abstract void pickUpEvent(ActionEvent actionEvent);

	protected abstract ReservationEvent createNewEvent(Date since, Date to);

	public T getItem()
	{
		return item;
	}

	@PostConstruct
	public void init()
	{
		settingsFactory = SpringBeanFactory.getInstance().getBean(
				SettingsFactory.class);
		id = loadIdentifier();
		eventModel = getModel(id);
		item = loadItem(id);
	}

	public Integer getId()
	{
		return id;
	}

	protected Integer loadIdentifier()
	{
		String userIdAsString = FacesContext.getCurrentInstance()
				.getExternalContext().getRequestParameterMap().get("id");
		try
		{
			return Integer.valueOf(userIdAsString);
		} catch (NumberFormatException ex)
		{
			return null;
		}
	}

	public ScheduleModel getEventModel()
	{
		return eventModel;
	}

	public void deleteEvent(ActionEvent actionEvent)
	{
		Reservation reservation = event.getPeriod().getReservation();
		getReservationService().delete(reservation);
	}

	public void addEvent(ActionEvent actionEvent)
	{
		// Do not accept changes to past
		if (event.to().isBefore(LocalDateTime.now()))
		{
			return;
		}

		if (getEvent().getPeriod() == null)
		{
			createNewEvent(getEvent());
		} else
		{
			modifyTimeEvent(getEvent());
		}

	}

	public void editEvent(ActionEvent actionEvent)
	{
		boolean hideDialog = true;
		// Period period = getPeriodService().get();
		Period period = event.getPeriod();
		// period.setValid(event.isEnabled());

		if (period.isValid() != event.isEnabled())
		{
			getPeriodService().setValid(period, event.isEnabled());
		}
		Reservation res = period.getReservation();
		if (!res.getUser().equals(event.getUser()))
		{
			res.setUser(event.getUser());

			getReservationService().update(res);
		}

		LocalDateTime newSince = new LocalDateTime(event.getStartDate());
		LocalDateTime newTo = new LocalDateTime(event.getEndDate());

		if (!event.getPeriod().getFromDate().isEqual(newSince)
				|| !event.getPeriod().getToDate().isEqual(newTo))
		{
			try
			{
				TimeSlot timeslot = new TimeSlot(newSince, newTo);

				getReservationService().mergeMultiple(period, timeslot);
			} catch (OccupiedReservationTimeSlotException ex)
			{
				User user = getPeriodService().get(ex.getPeriodId())
						.getReservation().getUser();
				String name = user.getFirstName() + " " + user.getSurname();

				errorMessage = LocaleMessageFactory
						.getLocale("view.calendar.error.blocked") + name;
				hideDialog = false;
			}
		}

		if (hideDialog)
		{
			RequestContext.getCurrentInstance().execute("editDialog.hide()");
			RequestContext.getCurrentInstance().execute("myschedule.update()");
		}
	}

	private void modifyTimeEvent(ReservationEvent event)
	{
		try
		{
			TimeSlot timeslot = new TimeSlot(event.since(), event.to());
			getReservationService().mergeMultiple(event.getPeriod(), timeslot);
			errorMessage = "";

		} catch (OccupiedReservationTimeSlotException ex)
		{
			User user = getPeriodService().get(ex.getPeriodId())
					.getReservation().getUser();
			String name = user.getFirstName() + " " + user.getSurname();

			errorMessage = LocaleMessageFactory
					.getLocale("view.calendar.error.blocked") + name;
		}
	}

	public String getErrorMessage()
	{
		return errorMessage;
	}

	private void createNewEvent(ReservationEvent event)
	{
		Reservation reservation = ReservationFactory.createReservation(event);
		errorMessage = "";
		try
		{
			getReservationService().create(reservation);
			RequestContext.getCurrentInstance().execute("eventDialog.hide()");
		} catch (OccupiedReservationTimeSlotException e)
		{
			User user = getPeriodService().get(e.getPeriodId())
					.getReservation().getUser();
			String name = user.getFirstName() + " " + user.getSurname();
			errorMessage = LocaleMessageFactory
					.getLocale("view.calendar.error.blocked") + name;
			FacesContext.getCurrentInstance().addMessage(
					"form",
					new FacesMessage(FacesMessage.SEVERITY_FATAL, errorMessage,
							null));
		}
	}

	protected ObjectService getObjectservice()
	{
		if (objectService == null)
		{
			objectService = SpringBeanFactory.getInstance().getBean(
					"objectService", ObjectService.class);
		}
		return objectService;
	}

	public void onEventSelect(SelectEvent selectEvent)
	{
		event = (ReservationEvent) selectEvent.getObject();
	}

	public void onDateSelect(SelectEvent selectEvent)
	{
		Date since = (Date) selectEvent.getObject();
		LocalDateTime sinceJoda = new LocalDateTime(since);

		if (sinceJoda.isBefore(LocalDateTime.now()))
		{
			sinceJoda = new LocalDateTime(getCurrentDate());
		}
		LocalDateTime toJoda = sinceJoda.plus(settingsFactory
				.getMinimumReservationDuration());

		event = createNewEvent(sinceJoda.toDate(), toJoda.toDate());
	}

	public void onEventMove(ScheduleEntryMoveEvent event)
	{
		this.event = (ReservationEvent) event.getScheduleEvent();
		addEvent(null);
	}

	public void onEventResize(ScheduleEntryResizeEvent event)
	{
		this.event = (ReservationEvent) event.getScheduleEvent();
		addEvent(null);
	}

	public ReservationEvent getEvent()
	{
		if (event == null)
		{
			event = createNewEvent(getCurrentDate(), getMinToDate());
		}
		return event;
	}

	public void setEvent(ReservationEvent event)
	{
		this.event = event;
	}

	public Integer getUserId()
	{
		return id;
	}

	public void setUserId(Integer userId)
	{
		this.id = userId;
	}

	public Date getCurrentDate()
	{
		return LocalDateTime.now().toDate();
	}

	// public Date getMaxSinceDate()
	// {
	// settingsFactory.getMinimumReservationDuration();
	// // TODO: proc pet minut,k de to konfigurovat? Musí se to přičítat pdole
	// // toho kde jsem to vyselektil!!!!
	// return DateTime.now().plusMinutes(5).plusWeeks(50).toDate();
	// }

	public Date getMinToDate()
	{
		return DateTime.now()
				.plus(settingsFactory.getMinimumReservationDuration()).toDate();
	}

	public Date getMaxToDate()
	{
		return new LocalDateTime(getMinToDate()).plusWeeks(1).toDate();
	}

	public int getMaximumWeeks()
	{
		return settingsFactory.getMaximumPeriodicalCount();
	}

	public String getDateViewPattern()
	{
		return DATE_VIEW_PATTERN;
	}

	protected ReservationService getReservationService()
	{
		if (reservationService == null)
		{
			reservationService = SpringBeanFactory.getInstance().getBean(
					"reservationService", ReservationService.class);
		}
		return reservationService;
	}

	protected UserService getUserService()
	{
		if (userService == null)
		{
			userService = SpringBeanFactory.getInstance().getBean(
					"userService", UserService.class);
		}
		return userService;
	}

	protected PeriodService getPeriodService()
	{
		if (periodService == null)
		{
			periodService = SpringBeanFactory.getInstance().getBean(
					"periodService", PeriodService.class);
		}

		return periodService;
	}

	public void returnBackEvent(ActionEvent actionEvent)
	{
		getPeriodService().returnItBack(getEvent().getPeriod());
	}

	/**
	 * dirty hack which came from JSF standard. when form validation failed it
	 * is not possible to update form items. So we use primefaces reset feature here
	 * (which is actualy out of standard)
	 * 
	 * see https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1060
	 * 
	 * @param event
	 */

	public void resetCreatePanel(CloseEvent event)
	{
		RequestContext.getCurrentInstance().reset("commitForm:eventDetails");
	}
	
	public void resetEditPanel(CloseEvent event)
	{
		RequestContext.getCurrentInstance().reset("editForm:eventEdit");
	}
}
